package com.wangcanxuan.www.proxy;

import com.wangcanxuan.www.annotation.orm.*;
import com.wangcanxuan.www.dao.SqlSession;
import com.wangcanxuan.www.dao.SqlSessionImpl;
import com.wangcanxuan.www.proxy.constants.ReturnDataType;


import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * dao层代理类
 *
 * @author HHLJ
 * @date 2022/04/05
 */
public class DaoProxy implements InvocationHandler {


    /**
     * sqlCRUD封装方法类
     */
    private static final SqlSession SQL_PORT = new SqlSessionImpl();

    /**
     * 代理目标
     */
    private Object target;

    /**
     * 代理对象
     */
    private Object proxy;

    /**
     * 该map相当于一个对本代理类的缓存，也算是一种单例模式，防止生成过多的代理类
     * 假如我多次调用被代理的类，只用创建第一次代理对象，在以后调用的时候会从map集合里
     * 拿出对应我被代理类的class对象的FunctionProxy对象
     * 使用ReentrantLock保证并发情况下的并发安全，将返回写在try-finally外减小锁的粒度
     */
    private static final HashMap<Class<?>, DaoProxy> INVOCATION_HANDLER_HASH_MAP = new HashMap<>();

    /**
     * 锁
     */
    private static final Lock LOCK = new ReentrantLock();

    /**
     * 创建代理对象
     * 代理对象proxy的类型是com.sun.proxy.$Proxy0
     *
     * @param proxyClazz 代理clazz
     * @return {@link T}
     */
    public static <T> T getMapper(Class<T> proxyClazz) {
        // 从集合中获取invocationHandler，即FunctionProxy对象
        DaoProxy invocationHandler = INVOCATION_HANDLER_HASH_MAP.get(proxyClazz);
        LOCK.lock();
        try {
            // 如果invocationHandler为空，为其初始化
            if (invocationHandler == null) {
                invocationHandler = new DaoProxy();
                invocationHandler.setProxy(Proxy.newProxyInstance(proxyClazz.getClassLoader(), new Class[]{proxyClazz}, invocationHandler));

                // 在map中put被代理类的类对象和代理类的对象即invocationHandler
                INVOCATION_HANDLER_HASH_MAP.put(proxyClazz, invocationHandler);
            }
        } finally {
            LOCK.unlock();
        }
        return (T) invocationHandler.getProxy();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 如果接口方法有Select注解，根据返回类型调用封装好的查询操作
        if (method.isAnnotationPresent(Select.class)) {
            // 获得当前方法的注解对象
            Select selectAnnotation = method.getAnnotation(Select.class);
            // 获得当前方法的返回值
            ReturnType returnTypeAnnotation = method.getAnnotation(ReturnType.class);
            ReturnDataType returnDataType = returnTypeAnnotation.returnType();

            // 判断注解对象中是否拥有sql语句
            if ("".equals(selectAnnotation.sql())) {
                // 生成sql语句
                String selectSql = SQL_PORT.getSelectSql(args[0]);
                if (returnDataType.equals(ReturnDataType.OBJECT)) {
                    return SQL_PORT.selectObject(selectSql, null, args[0], args[0].getClass());
                }
                if (returnDataType.equals(ReturnDataType.FIELD)) {
                    return SQL_PORT.selectField(selectSql, null, args[0]);
                }
                if (returnDataType.equals(ReturnDataType.LIST)) {
                    return SQL_PORT.selectList(selectSql, null, args[0], args[0].getClass());
                }
            } else {
                // 获得注解中的sql语句
                String selectSql = selectAnnotation.sql();
                if (returnDataType.equals(ReturnDataType.OBJECT)) {
                    return SQL_PORT.selectObject(selectSql, args, null, method.getReturnType());
                }
                if (returnDataType.equals(ReturnDataType.FIELD)) {
                    return SQL_PORT.selectField(selectSql, args, null);
                }
                if (returnDataType.equals(ReturnDataType.LIST)) {
                    return SQL_PORT.selectList(selectSql, args, null, (Class<?>) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]);
                }
            }


        }

        // 如果接口方法有Insert注解，调用封装好的添加操作
        if (method.isAnnotationPresent(Insert.class)) {
            // 获得当前方法的注解对象
            Insert insertAnnotation = method.getAnnotation(Insert.class);
            // 判断是否有sql语句，传入实体类对象
            if ("".equals(insertAnnotation.sql())) {
                return SQL_PORT.insert(args[0]);
            }
            // 有手写的sql
            return SQL_PORT.executeUpdate(insertAnnotation.sql(), null, args[0]);
        }

        // 如果接口方法有Delete注解，调用封装好的删除操作
        if (method.isAnnotationPresent(Delete.class)) {
            // 获得当前方法的注解对象
            Delete deleteAnnotation = method.getAnnotation(Delete.class);
            // 判断是否有sql语句，传入实体类对象
            if ("".equals(deleteAnnotation.sql())) {
                return SQL_PORT.delete(args[0]);
            }
            // 有手写的sql
            return SQL_PORT.executeUpdate(deleteAnnotation.sql(), null, args[0]);
        }

        // 如果接口方法有Update注解，调用封装好的更新操作
        if (method.isAnnotationPresent(Update.class)) {
            // 判断是否有sql语句
            Update updateAnnotation = method.getAnnotation(Update.class);
            // 判断是否有sql语句
            if ("".equals(updateAnnotation.sql())) {
                // 判断传入的参数是否仅有id，传入实体类对象
                if (args.length == 1) {
                    return SQL_PORT.updateById(args[0]);
                }
                // 通过传进实体类对象和map集合，本项目并没用到过
                return SQL_PORT.updateByMap(args[0], (Map<String, Object>) args[1]);
            }
            // 手写sql实现更新
            return SQL_PORT.executeUpdate(updateAnnotation.sql(), args, null);
        }
        return method.invoke(target, args);
    }

    public Object getTarget() {
        return target;
    }

    public void setTarget(Object target) {
        this.target = target;
    }

    public Object getProxy() {
        return proxy;
    }

    public void setProxy(Object proxy) {
        this.proxy = proxy;
    }
}
